package iservice

import (
	"fmt"
	"time"

	. "starsine.cn/appTpl/cmn/defs"
	"starsine.cn/appTpl/cmn/logs"
	"starsine.cn/appTpl/cmn/pubsub"
	"starsine.cn/appTpl/cmn/timermgr"
	"starsine.cn/appTpl/svccenter/registry/grpc/client"
	"starsine.cn/appTpl/svcregistry/grpc/svcregistryproto"
)

type GRpcService struct {
	InstanceID  string
	SerialNum   int32
	ServiceType string
	Name        string
	UserSpace   string
	Health
	HeartbeatMng
	timerID int64
	appCtxt *AppContext
	tmgr    *timermgr.TimerMgr

	data SvcData

	conn *client.GRpcClient
}

func (t GRpcService) String() (str string) {
	str += fmt.Sprintln("GRpcService Info:")
	str += fmt.Sprintln("UserSpace: ", t.UserSpace)
	str += fmt.Sprintln("ServiceType: ", t.ServiceType)
	str += fmt.Sprintln("Name: ", t.Name)
	str += fmt.Sprintln("InstanceID: ", t.InstanceID)
	str += fmt.Sprintln("Health: ", t.Health)
	str += fmt.Sprintln(t.HeartbeatMng)

	str += fmt.Sprintln("data: ", t.data)

	return str
}

func CreateGRpcService(vType, vName, userSpace string, data SvcData, appCtxt *AppContext, tmgr *timermgr.TimerMgr) *GRpcService {
	p := new(GRpcService)

	p.InstanceID = SvcInstanceID(vType, vName, userSpace)
	p.SerialNum = 0
	p.appCtxt = appCtxt
	p.tmgr = tmgr
	p.ServiceType = vType
	p.Name = vName
	p.UserSpace = userSpace
	p.Health = HealthGood
	p.HeartbeatMng = HeartbeatMng{
		Duration:  HEARTBEAT_DURATION_SEC,
		MaxTimes:  HEARTBEAT_MAXTIMES,
		FailTimes: 0,
	}
	p.timerID = -1
	p.data = data

	logs.Debugf("service(%s-%s-%s) created", vType, vName, userSpace)
	return p
}

func (p *GRpcService) Start(c interface{}) {
	p.conn = client.CreateGRpcClient(p.appCtxt, p.data.RpcAddr)
	p.conn.Start()
	p.heartbeatCheck()
}

func (p *GRpcService) End() {
	p.tmgr.CancelTimer(p.timerID)
	p.conn.End()
}

func (p *GRpcService) MsgHandler(msg []byte) {}

func (p *GRpcService) GetServiceType() string {
	return p.ServiceType
}

func (p *GRpcService) GetName() string {
	return p.Name
}

func (p *GRpcService) GetInstanceID() string {
	return p.InstanceID
}

func (p *GRpcService) GetUserSpace() string {
	return p.UserSpace
}

func (p *GRpcService) GetRpcAddr() string {
	return p.data.RpcAddr
}

func (p *GRpcService) GetVersion() int32 {
	return p.data.Version
}

func (p *GRpcService) GetHealthy() Health {
	return p.Health
}

func (p *GRpcService) GetSvcData() SvcData {
	return p.data
}

func (p *GRpcService) Send(topic string, content interface{}) error {
	pm := pubsub.TopicParse(topic)
	if pm["topic"] == "svc-register" {
		r, err := p.conn.PubSvcRegister(&svcregistryproto.PubSvcRegisterReq{
			Topic: topic,
			Addr:  content.([]*svcregistryproto.SvcAddr),
		})
		if err != nil {
			logs.Warnf("send svc-register to svc(%s) fail, err: %s", p.InstanceID, err)
		} else {
			logs.Warnf("send svc-register to svc(%s) ok(%+v) ", p.InstanceID, r)
		}
		return err
	}
	if pm["topic"] == "svc-update" {
		r, err := p.conn.PubSvcUpdate(&svcregistryproto.PubSvcUpdateReq{
			Topic: topic,
			Data:  content.(*svcregistryproto.SvcUpdateInfo),
		})
		if err != nil {
			logs.Warnf("send svc-update to svc(%s) fail, err: %s", p.InstanceID, err)
		} else {
			logs.Warnf("send svc-update to svc(%s) ok(%+v) ", p.InstanceID, r)
		}
		return err
	}

	logs.Warnf("svc send topic(%s) with content: %+v", topic, content)
	return fmt.Errorf("svc send unknown topic(%s) with content: %+v", topic, content)
}

func (p *GRpcService) heartbeatCheck() {
	p.timerID = p.tmgr.AddPeriodTimer(
		time.Duration(p.HeartbeatMng.Duration),
		timermgr.NewOnTimeOut(p.heartbeatCheckCB),
	)

	logs.Debugf("start heartbeatCheck svc(%s) name(%s) period(%d sec)", p.InstanceID, p.Name, p.HeartbeatMng.Duration)
}

func (p *GRpcService) heartbeatCheckCB(params interface{}) {
	p.SerialNum++

	r, err := p.conn.SvcHeartbeat(&svcregistryproto.HeartbeatReq{
		InstanceID: p.InstanceID,
	})
	if err != nil {
		logs.Warnf("heartbeat to svc: %s fail, error: %s", p.InstanceID, err)
		goto _fail
	} else {
		if !r.Succ {
			logs.Warnf("heartbeat to svc: %s fail, error: %s", p.InstanceID, "succ==fale")
			goto _fail
		}
		p.ResetHeartbeat()
		logs.Debugf("heartbeat to svc(%s) ok", p.InstanceID)
		return
	}

_fail:
	p.HeartbeatMng.FailTimes++
	if p.HeartbeatMng.FailTimes >= p.HeartbeatMng.MaxTimes {
		p.Health = HealthDEAD
		p.tmgr.CancelTimer(p.timerID)
		logs.Warnf("service healthy set to %s, timer id: %d canceled", p.Health, p.timerID)
	} else {
		p.Health = HealthBAD
		logs.Warnf("service healthy set to %s", p.Health)
	}
	logs.Warnf("heartbeat to %s fail count %d", p.InstanceID, p.HeartbeatMng.FailTimes)
}

func (p *GRpcService) ResetHeartbeat() {
	p.HeartbeatMng.FailTimes = 0
	p.Health = HealthGood
}
